package com.nulldev.util.web.YouTubeExtractor.util;

import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.RandomUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.data.Variables;
import com.nulldev.util.languages.json.uniapi.UniJsonIterableValue;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.Macros;

public class YouTubeExtractorUtil {

	private static final Logger logger = LoggerFactory.getLogger();
	private static final String[] BAD_JAVASCRIPT_CHARS = new String[]
		{ "in", "if", "of" };
	private static final String[] OTHER_BAD_JAVASCRIPT_CHARS = new String[]
		{ "{", "}", "|", ",", "[", "]", ".", "\"", "(", ")", ":", "+", ";" };
	private static final String[] ALLOWED_STATUS_VALUES =
		{ "OK", "REEL_ITEM_WATCH_STATUS_SUCCEEDED" };
	private static final String CPN_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
	private static final String CPN_REDUCED_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

	/* */

	public static final int DURATION_TYPE_SHOW_AS_SHORT = -4;

	/* */

	public static String getYTString(final UniJsonValue object) {
		Variables.requireNonNull(object, "object");
		if (object.contains("simpleText")) {
			return object.getString("simpleText");
		} else if (object.contains("elementsAttributedString")) {
			return object.get("elementsAttributedString").getString("content");
		} else if (object.contains("content")) {
			return object.getString("content");
		} else {
			final UniJsonValue runs = object.get("runs");
			if (runs == null || runs.isNull() || runs.size() == 0) {
				return null;
			} else if (runs.size() <= 1) {
				return runs.get(0).getString("text");
			} else {
				final StringBuilder str = new StringBuilder();
				for (int i = 0; i < runs.size(); i++) {
					str.append(runs.get(i).getString("text"));
				}
				return str.toString();
			}
		}
	}

	public static String getYTStringF(final UniJsonValue object, final String fallback) {
		Variables.requireNonNull(object, "object");
		if (object.contains("simpleText")) {
			return object.getString("simpleText", fallback);
		} else if (object.contains("elementsAttributedString")) {
			return object.get("elementsAttributedString").getString("content", fallback);
		} else if (object.contains("content")) {
			return object.getString("content", fallback);
		} else {
			final UniJsonValue runs = object.get("runs");
			if (runs == null || runs.isNull() || runs.size() == 0) {
				return fallback;
			} else if (runs.size() == 1) {
				return runs.get(0).getString("text", fallback);
			} else {
				final StringBuilder str = new StringBuilder();
				for (int i = 0; i < runs.size(); i++) {
					str.append(runs.get(i).getString("text"));
				}
				return str.toString();
			}
		}
	}

	public static String getYTContinuation(final UniJsonValue object) {
		Variables.requireNonNull(object, "object");
		if (object.contains("continuations")) {
			final UniJsonValue firstObj = object.get("continuations").asArray().get(0);
			if (firstObj.contains("reloadContinuationData")) {
				return firstObj.get("reloadContinuationData").getString("continuation");
			} else {
				return firstObj.get("nextContinuationData").getString("continuation");
			}
		} else if (object.contains("contents")) {
			final UniJsonValue c0 = object.get("contents")
					/* */
					.asArray()
					/* */
					.get(0);
			if (c0 == null || c0.isNull()) {
				/* This can fail in situations like where comments are disabled. */
				logger.dwarn("[getYTContinuation] [contents] Failed to find contents!");
				return null;
			} else if (!c0.contains("continuationItemRenderer")) {
				logger.warn("[getYTContinuation] [contents] Failed to find a renderer!");
				return null;
			} else {
				return c0.get("continuationItemRenderer")
						/* */
						.get("continuationEndpoint")
						/* */
						.get("continuationCommand")
						/* */
						.getString("token");
			}
		} else {
			logger.error("[getYTContinuation] Failed to find a continuation candidate!");
			logger.derror(object);
			return null;
		}
	}

	public static boolean isYTMusicVideo(final String str) {
		if (str == null || str.isEmpty())
			return false;
		return str.startsWith("Provided to YouTube by") && str.endsWith("Auto-generated by YouTube.");
	}

	/* Lifted directly from https://github.com/yt-dlp/yt-dlp/pull/4470/files */

	public static final String YT_DEFAULT_LANG_CODE = "en";
	public static final String[] YT_LANG_CODES =
		{ "af", "az", "id", "ms", "bs", "ca", "cs", "da", "de", "et", "en-IN", "en-GB", "en", "es", "es-419", "es-US", "eu", "fil", "fr", "fr-CA", "gl", "hr",
				"zu", "is", "it", "sw", "lv", "lt", "hu", "nl", "no", "uz", "pl", "pt-PT", "pt", "ro", "sq", "sk", "sl", "sr-Latn", "fi", "sv", "vi", "tr",
				"be", "bg", "ky", "kk", "mk", "mn", "ru", "sr", "uk", "el", "hy", "iw", "ur", "ar", "fa", "ne", "mr", "hi", "as", "bn", "pa", "gu", "or", "ta",
				"te", "kn", "ml", "si", "th", "lo", "my", "ka", "am", "km", "zh-CN", "zh-TW", "zh-HK", "ja", "ko" };

	public static final String validLangCodeOrFallback(final String langCode) {
		if (ArrayUtils.StringArrayUtils.containsString(YT_LANG_CODES, langCode)) {
			return langCode;
		} else {
			return YT_DEFAULT_LANG_CODE;
		}
	}

	/* */

	private static boolean isValid(final String r) {
		for (final char c : Variables.requireNonNullObject(r, "r").toCharArray()) {
			if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
				return false;
			}
		}
		return true;
	}

	private static final boolean checkInvalidCharacters(final String r) {
		Variables.requireNonNull(r, "r");
		if (ArrayUtils.StringArrayUtils.containsInStringOrStartsWith_NoCase(BAD_JAVASCRIPT_CHARS, r))
			return true;
		if (ArrayUtils.StringArrayUtils.containsInString(OTHER_BAD_JAVASCRIPT_CHARS, r))
			return true;
		return false;
	}

	public static String validateFunctionName(String r) {
		final String _r = Variables.requireNonNullObject(r, "r");
		if ((r.startsWith(".") || r.startsWith("=") || r.startsWith(";") || r.startsWith("$") || r.startsWith("\n")) && r.length() >= 2)
			r = r.substring(1);
		if ((r.endsWith(".") || r.endsWith(";") || r.endsWith("=")) && r.length() >= 2)
			r = r.substring(0, r.length() - 1);
		if ((r == null || r.isEmpty()) || !isValid(r) || checkInvalidCharacters(r) || StringsUtil.isNumber(r) || Character.isDigit(r.charAt(0))) {
			logger.warn("validateFunctionName: Dropped invalid decipher function name: " + r + " (org: " + _r + ")");
			return StringsUtil.randomStringAlphabet(2);
		}
		return r;
	}

	public static boolean hasErrors(final UniJsonValue uv) {
		if (uv == null)
			return true;
		if (uv.contains("error"))
			return true;
		if (uv.contains("status") && !ArrayUtils.contains(ALLOWED_STATUS_VALUES, uv.getString("status", "OK"))) {
			logger.error("Invalid status: " + uv.getString("status", "OK"));
			return false;
		}
		return false;
	}

	public static String makeDummyCPN() {
		final StringBuilder sb = new StringBuilder(16);
		for (int i = 0; i < 16; i++) {
			sb.append(CPN_ALPHABET.charAt(RandomUtil.randInt(0, 256) & 63));
		}
		return sb.toString();
	}

	public static String makeDummyReducedCPN() {
		return makeDummyReducedCPN(16);
	}

	public static String makeDummyReducedCPN(final int length) {
		final int x = (length <= 0 ? 16 : length);
		final StringBuilder sb = new StringBuilder(x);
		for (int i = 0; i < x; i++) {
			sb.append(CPN_REDUCED_ALPHABET.charAt(RandomUtil.randInt(0, 256) & 61));
		}
		return sb.toString();
	}

	public static UniJsonValue getTabRendererWithContent(final UniJsonIterableValue arr) {
		Variables.requireNonNull(arr, "arr");
		for (final UniJsonValue val : arr) {
			if (!val.contains("tabRenderer"))
				continue;
			if (val.get("tabRenderer").contains("content"))
				return val;
		}
		return null;
	}

	public static String getTextFromViewModel(final UniJsonValue model) {
		Variables.requireNonNull(model, "model");
		Macros.__assert(model.isNull(), "model.isNull!");

		if (model.contains("text")) {
			return model.get("text").getString("content");
		} else if (model.contains("dynamicTextViewModel")) {
			final UniJsonValue dynamicTextViewModel = model.get("dynamicTextViewModel");

			if (dynamicTextViewModel.contains("text")) {
				return dynamicTextViewModel.get("text").getString("content");
			} else {
				logger.warn("[getTextFromViewModel] Failed to find text within dynamicTextViewModel!");
				return null;
			}
		} else {
			logger.warn("[getTextFromViewModel] Failed to find a valid view model!");
			return null;
		}
	}
}
